﻿<template>
    <div :id="chartID" style="width: 100%;height: 100%;"></div>
</template>
<script>
    export default {
        props: {
            chartHeight: { type: Number, default: 0 },
            chartData: { type: Array, default: () => [] }
        },
        data() {
            return {
                chartID: 'antvg6-' + new Date().getTime(),
                myChart: null
            };
        },
        watch: {
            chartHeight(val) {
                if (val && val > 0) {
                    this.repaint();
                };
            }
        },
        mounted() {
        },
        methods: {
            initChart() {
                if (!this.chartData) {
                    return;
                }
                if (this.myChart) {
                    this.repaint();
                    return;
                }
                insertCss(`
                  .g6-component-tooltip {
                    background-color: rgba(0,0,0, 0.65);
                    padding: 10px;
                    box-shadow: rgb(174, 174, 174) 0px 0px 10px;
                    width: fit-content;
                    color: #fff;
                    border-radius = 4px;
                  }
                `);

                const colors = {
                    B: '#5B8FF9',
                    R: '#F46649',
                    Y: '#EEBC20',
                    G: '#5BD8A6',
                    DI: '#A7A7A7',
                };

                //  组件props
                const props = {
                    data: this.chartData,
                    config: {
                        padding: [20, 50],
                        defaultLevel: 3,
                        defaultZoom: 0.8,
                        modes: { default: ['zoom-canvas', 'drag-canvas'] },
                    },
                };

                const container = document.getElementById(this.chartID);
                const width = container.scrollWidth;
                const height = container.scrollHeight || 500;

                // 默认配置
                const defaultConfig = {
                    width,
                    height,
                    modes: {
                        default: ['zoom-canvas', 'drag-canvas'],
                    },
                    fitView: true,
                    animate: true,
                    defaultNode: {
                        type: 'flow-rect',
                    },
                    defaultEdge: {
                        type: 'cubic-horizontal',
                        style: {
                            stroke: '#CED4D9',
                        },
                    },
                    layout: {
                        type: 'indented',
                        direction: 'LR',
                        dropCap: false,
                        indent: 300,
                        getWidth: () => {
                            return 220;
                        },
                        getHeight: () => {
                            return 60;
                        },
                    },
                };

                // 自定义节点、边
                const registerFn = () => {
                    /**
                     * 自定义节点
                     */
                    G6.registerNode(
                        'flow-rect',
                        {
                            shapeType: 'flow-rect',
                            draw(cfg, group) {
                                const {
                                    name = '',
                                    desc,
                                    percent,
                                    typeName,
                                    status,
                                    date,
                                    isYear
                                } = cfg;

                                const grey = '#CED4D9';
                                const rectConfig = {
                                    width: 202,
                                    height: 70,
                                    lineWidth: 1,
                                    fontSize: 12,
                                    fill: '#fff',
                                    radius: 4,
                                    stroke: grey,
                                    opacity: 1,
                                };

                                const nodeOrigin = {
                                    x: -rectConfig.width / 2,
                                    y: -rectConfig.height / 2,
                                };

                                const textConfig = {
                                    textAlign: 'left',
                                    textBaseline: 'bottom',
                                };

                                const rect = group.addShape('rect', {
                                    attrs: {
                                        x: nodeOrigin.x,
                                        y: nodeOrigin.y,
                                        ...rectConfig,
                                    },
                                });

                                const rectBBox = rect.getBBox();

                                // 顶部名称
                                const onlyShowTitle = typeName === '';
                                const titleText = group.addShape('text', {
                                    attrs: {
                                        ...textConfig,
                                        x: onlyShowTitle && isYear ? nodeOrigin.x + 70 : nodeOrigin.x + 12,
                                        y: onlyShowTitle ? nodeOrigin.y + 45 : nodeOrigin.y + 20,
                                        text: name.length > 24 ? name.substr(0, 24) + '...' : name,
                                        fontSize: onlyShowTitle ? 16 : 14,
                                        textAlign: 'left',
                                        fontWeight: '600',
                                        fill: '#000',
                                        cursor: 'pointer',
                                    },
                                    name: 'name-shape',
                                });

                                // 类型
                                const typeText = group.addShape('text', {
                                    attrs: {
                                        ...textConfig,
                                        x: nodeOrigin.x + 12,
                                        y: titleText.getBBox().maxY + 20,
                                        text: typeName,
                                        fontSize: 12,
                                        fill: '#b5b5b5'
                                    },
                                });

                                // 日期
                                const dateText = group.addShape('text', {
                                    attrs: {
                                        ...textConfig,
                                        x: rectBBox.maxX - 14,
                                        y: titleText.getBBox().maxY + 20,
                                        text: date,
                                        textAlign: 'right',
                                        fontSize: 12,
                                        fill: '#b5b5b5'
                                    },
                                });

                                // 备注
                                const descText = group.addShape('text', {
                                    attrs: {
                                        ...textConfig,
                                        x: nodeOrigin.x + 12,
                                        y: typeText.getBBox().maxY + 20,
                                        text: desc.length > 13 ? desc.substr(0, 13) + '...' : desc,
                                        fontSize: 12,
                                        fill: '#b5b5b5'
                                    },
                                    name: 'desc-shape'
                                });

                                // 完成百分比
                                const percentT = percent === 0 ? '' : `${(percent * 100).toFixed(2)}%`;
                                const percentText = group.addShape('text', {
                                    attrs: {
                                        ...textConfig,
                                        x: rectBBox.maxX - 14,
                                        y: rectBBox.maxY - 12,
                                        text: onlyShowTitle ? '' : percentT,
                                        fontSize: 12,
                                        textAlign: 'right',
                                        fill: colors[status],
                                    },
                                });

                                // bottom line background
                                const bottomBackRect = group.addShape('rect', {
                                    attrs: {
                                        x: nodeOrigin.x,
                                        y: rectBBox.maxY - 4,
                                        width: rectConfig.width,
                                        height: 4,
                                        radius: [0, 0, rectConfig.radius, rectConfig.radius],
                                        fill: '#E0DFE3',
                                    },
                                });

                                // bottom percent
                                const bottomRect = group.addShape('rect', {
                                    attrs: {
                                        x: nodeOrigin.x,
                                        y: rectBBox.maxY - 4,
                                        width: percent * rectBBox.width,
                                        height: 4,
                                        radius: [0, 0, 0, rectConfig.radius],
                                        fill: colors[status],
                                    },
                                });

                                // collapse rect
                                if (cfg.children && cfg.children.length) {
                                    group.addShape('rect', {
                                        attrs: {
                                            x: rectConfig.width / 2 - 8,
                                            y: -8,
                                            width: 16,
                                            height: 16,
                                            stroke: 'rgba(0, 0, 0, 0.25)',
                                            cursor: 'pointer',
                                            fill: '#fff',
                                        },
                                        name: 'collapse-back',
                                        modelId: cfg.id,
                                    });

                                    // collpase text
                                    group.addShape('text', {
                                        attrs: {
                                            x: rectConfig.width / 2,
                                            y: -1,
                                            textAlign: 'center',
                                            textBaseline: 'middle',
                                            text: '-',
                                            fontSize: 16,
                                            cursor: 'pointer',
                                            fill: 'rgba(0, 0, 0, 0.25)',
                                        },
                                        name: 'collapse-text',
                                        modelId: cfg.id,
                                    });
                                }

                                this.drawLinkPoints(cfg, group);
                                return rect;
                            },
                            update(cfg, item) {
                                const { level, status, name } = cfg;
                                const group = item.getContainer();
                                let mask = group.find(ele => ele.get('name') === 'mask-shape');
                                let maskLabel = group.find(ele => ele.get('name') === 'mask-label-shape');
                                if (level === 0) {
                                    group.get('children').forEach(child => {
                                        if (child.get('name')?.includes('collapse')) return;
                                        child.hide();
                                    })
                                    if (!mask) {
                                        mask = group.addShape('rect', {
                                            attrs: {
                                                x: -101,
                                                y: -30,
                                                width: 202,
                                                height: 60,
                                                opacity: 0,
                                                fill: colors[status]
                                            },
                                            name: 'mask-shape',
                                        });
                                        maskLabel = group.addShape('text', {
                                            attrs: {
                                                fill: '#fff',
                                                fontSize: 20,
                                                x: 0,
                                                y: 10,
                                                text: name.length > 28 ? name.substr(0, 16) + '...' : name,
                                                textAlign: 'center',
                                                opacity: 0,
                                            },
                                            name: 'mask-label-shape',
                                        });
                                        const collapseRect = group.find(ele => ele.get('name') === 'collapse-back');
                                        const collapseText = group.find(ele => ele.get('name') === 'collapse-text');
                                        collapseRect?.toFront();
                                        collapseText?.toFront();
                                    } else {
                                        mask.show();
                                        maskLabel.show();
                                    }
                                    mask.animate({ opacity: 1 }, 200);
                                    maskLabel.animate({ opacity: 1 }, 200);
                                    return mask;
                                } else {
                                    group.get('children').forEach(child => {
                                        if (child.get('name')?.includes('collapse')) return;
                                        child.show();
                                    })
                                    mask?.animate({ opacity: 0 }, {
                                        duration: 200,
                                        callback: () => mask.hide()
                                    });
                                    maskLabel?.animate({ opacity: 0 }, {
                                        duration: 200,
                                        callback: () => maskLabel.hide()
                                    });
                                }
                                this.updateLinkPoints(cfg, group);
                            },
                            setState(name, value, item) {
                                if (name === 'collapse') {
                                    const group = item.getContainer();
                                    const collapseText = group.find((e) => e.get('name') === 'collapse-text');
                                    if (collapseText) {
                                        if (!value) {
                                            collapseText.attr({
                                                text: '-'
                                            });
                                        } else {
                                            collapseText.attr({
                                                text: '+'
                                            });
                                        }
                                    }
                                }
                            },
                            getAnchorPoints() {
                                return [
                                    [0, 0.5],
                                    [1, 0.5],
                                ];
                            },
                        },
                        'rect',
                    );

                    G6.registerEdge(
                        'flow-cubic',
                        {
                            getControlPoints(cfg) {
                                let controlPoints = cfg.controlPoints; // 指定controlPoints
                                if (!controlPoints || !controlPoints.length) {
                                    const { startPoint, endPoint, sourceNode, targetNode } = cfg;
                                    const { x: startX, y: startY, coefficientX, coefficientY } = sourceNode
                                        ? sourceNode.getModel()
                                        : startPoint;
                                    const { x: endX, y: endY } = targetNode ? targetNode.getModel() : endPoint;
                                    let curveStart = (endX - startX) * coefficientX;
                                    let curveEnd = (endY - startY) * coefficientY;
                                    curveStart = curveStart > 40 ? 40 : curveStart;
                                    curveEnd = curveEnd < -30 ? curveEnd : -30;
                                    controlPoints = [
                                        { x: startPoint.x + curveStart, y: startPoint.y },
                                        { x: endPoint.x + curveEnd, y: endPoint.y },
                                    ];
                                }
                                return controlPoints;
                            },
                            getPath(points) {
                                const path = [];
                                path.push(['M', points[0].x, points[0].y]);
                                path.push([
                                    'C',
                                    points[1].x,
                                    points[1].y,
                                    points[2].x,
                                    points[2].y,
                                    points[3].x,
                                    points[3].y,
                                ]);
                                return path;
                            },
                        },
                        'single-line',
                    );
                };

                registerFn();

                const { data } = props;
                this.myChart = null;

                const initGraph = (data) => {
                    if (!data) {
                        return;
                    }
                    const { onInit, config } = props;
                    const tooltip = new G6.Tooltip({
                        // offsetX and offsetY include the padding of the parent container
                        offsetX: 20,
                        offsetY: 30,
                        // the types of items that allow the tooltip show up
                        // 允许出现 tooltip 的 item 类型
                        itemTypes: ['node'],
                        // custom the tooltip's content
                        // 自定义 tooltip 内容
                        getContent: (e) => {
                            const outDiv = document.createElement('div');
                            //outDiv.style.padding = '0px 0px 20px 0px';
                            const targetName = e.target.get('name');
                            var zd = targetName ? targetName.split('-')[0] : null;
                            const nodeName = zd ? e.item.getModel()[zd] : '';
                            let formatedNodeName = '';
                            for (let i = 0; i < nodeName.length; i++) {
                                formatedNodeName = `${formatedNodeName}${nodeName[i]}`;
                                if (i !== 0 && i % 20 === 0) formatedNodeName = `${formatedNodeName}<br/>`;
                            }
                            outDiv.innerHTML = `${formatedNodeName}`;
                            return outDiv;
                        },
                        shouldBegin: (e) => {
                            if (e.target.get('name') === 'name-shape' || e.target.get('name') === 'mask-label-shape') return true;
                            if (e.target.get('name') === 'desc-shape' || e.target.get('name') === 'mask-label-shape') return true;
                            return false;
                        },
                    });
                    this.myChart = new G6.TreeGraph({
                        container: this.chartID,
                        ...defaultConfig,
                        ...config,
                        plugins: [tooltip],
                    });
                    if (typeof onInit === 'function') {
                        onInit(this.myChart);
                    }
                    this.myChart.data(data);
                    this.myChart.render();

                    const handleCollapse = (e) => {
                        const target = e.target;
                        const id = target.get('modelId');
                        const item = this.myChart.findById(id);
                        const nodeModel = item.getModel();
                        nodeModel.collapsed = !nodeModel.collapsed;
                        this.myChart.layout();
                        this.myChart.setItemState(item, 'collapse', nodeModel.collapsed);
                    };
                    this.myChart.on('collapse-text:click', (e) => {
                        handleCollapse(e);
                    });
                    this.myChart.on('collapse-back:click', (e) => {
                        handleCollapse(e);
                    });

                    // 监听画布缩放，缩小到一定程度，节点显示缩略样式
                    let currentLevel = 1;
                    const briefZoomThreshold = Math.max(this.myChart.getZoom(), 0.5);
                    this.myChart.on('viewportchange', e => {
                        if (e.action !== 'zoom') return;
                        return;
                        const currentZoom = this.myChart.getZoom();
                        let toLevel = currentLevel;
                        let fullscreen = width > 1500 ? 0.5 : 0.75;
                        if (currentZoom < (briefZoomThreshold * fullscreen)) {
                            toLevel = 0;
                        } else {
                            toLevel = 1;
                        }
                        if (toLevel !== currentLevel) {
                            currentLevel = toLevel;
                            this.myChart.getNodes().forEach(node => {
                                this.myChart.updateItem(node, {
                                    level: toLevel
                                })
                            })
                        }
                    });
                };

                initGraph(data);

                if (typeof window !== 'undefined') {
                    window.onresize = () => {
                        if (!this.myChart || this.myChart.get('destroyed')) return;
                        if (!container || !container.scrollWidth || !container.scrollHeight) return;
                        this.myChart.changeSize(container.scrollWidth, container.scrollHeight);
                    };
                }
            },
            repaint() {
                if (this.myChart) {
                    this.myChart.clear();
                    this.myChart.destroy();
                    this.myChart = null;
                    this.initChart();
                }
            },
            downloadImage(name) {
                if (this.myChart) {
                    window.oldRatio = window.devicePixelRatio;
                    window.devicePixelRatio = 5;
                    this.myChart.downloadFullImage(name, 'image/png', {
                        padding: 30
                    });
                    setTimeout(() => {
                        window.devicePixelRatio = window.oldRatio;
                    }, 200);
                }
            }
        },
        template: `#template`
    };
</script>
